/*
 *MIT License
 *
 *Copyright (c) 2019 chenshuai cs4380@163.com
 *
 *Permission is hereby granted, free of charge, to any person obtaining a copy
 *of this software and associated documentation files (the "Software"), to deal
 *in the Software without restriction, including without limitation the rights
 *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *copies of the Software, and to permit persons to whom the Software is
 *furnished to do so, subject to the following conditions:
 *
 *The above copyright notice and this permission notice shall be included in all
 *copies or substantial portions of the Software.
 *
 *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 *SOFTWARE.
 */

package com.cs.cslc.auth.realm;

import com.alibaba.fastjson.JSON;
import com.cs.cslc.admin.bean.JWTUserBean;
import com.cs.cslc.auth.service.UserService;
import com.cs.cslc.auth.utils.JWTUtil;
import com.cs.cslc.common.constant.JwtUserConstant;
import com.cs.cslc.common.constant.RedisKeysConstant;
import com.cs.cslc.common.enums.AuthenticationEnums;
import com.cs.cslc.common.pojo.AuthInfoExceptionDTO;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;

/**
 * ShiroDatabaseRealm 认证授权业务数据验证.
 *
 * @author cs4380 https://gitee.com/xhbug_cs4380  cs4380@163.com
 * @version 1.0
 * @since 2019-05-06 19:41
 */
public class ShiroDatabaseRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 此方法必须重写，否则Shiro会报提示错误：
     * Please ensure that the appropriate Realm implementation is configured correctly or
     * that the realm accepts AuthenticationTokens of this type.
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }


    /**
     * 验证用户的权限
     * TODO 暂时没用
     *
     * @param principals 用户的token
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return new SimpleAuthorizationInfo();
    }

    /**
     * 验证token的信息是否正确，错误抛出异常
     *
     * @param authToken 用户token
     * @return
     * @throws AuthenticationException 错误信息数据格式："401001:错误信息"
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authToken) throws AuthenticationException {
        String token = (String) authToken.getCredentials();
        // 反解token获得account
        String account = JWTUtil.getJwtValue(token, JwtUserConstant.JWT_KEY_USER_ACCOUNT);

        // token不存在账户名
        if (null == account) {
            throw new AuthenticationException(JSON.toJSONString(AuthInfoExceptionDTO.builder().exceptionType(AuthInfoExceptionDTO.LOGIN_EXCEPTION_TYPE)
                    .message(AuthenticationEnums.EXPIRED_TOKEN.getValue())
                    .statues(AuthenticationEnums.EXPIRED_TOKEN.getCode())
                    .build()));
        }
        // 查询redis中token匹配，判断是否在在其他地方登陆
        String redisToken = stringRedisTemplate.opsForValue().get(RedisKeysConstant.USER_TOKENS + account);
        // 在其他端已登出，或者token过期
        if (StringUtils.isBlank(redisToken) || !token.equals(redisToken)) {
            throw new AuthenticationException(JSON.toJSONString(AuthInfoExceptionDTO.builder().exceptionType(AuthInfoExceptionDTO.LOGIN_EXCEPTION_TYPE)
                    .message(AuthenticationEnums.EXPIRED_TOKEN.getValue())
                    .statues(AuthenticationEnums.EXPIRED_TOKEN.getCode())
                    .build()));
        }
        // 判断用户是否禁用
        JWTUserBean userBean = userService.getUserBean(account);
        if (null == userBean) {
            throw new AuthenticationException(JSON.toJSONString(AuthInfoExceptionDTO.builder().exceptionType(AuthInfoExceptionDTO.AUTH_EXCEPTION_TYPE)
                    .message(AuthenticationEnums.DISABLED_ACCOUNT.getValue())
                    .statues(AuthenticationEnums.DISABLED_ACCOUNT.getCode())
                    .build()));
        }
        return new SimpleAuthenticationInfo(token, token, getName());
    }
}